Skip to main content

Inventory transfer

Business scenario

When a merchant moves physical stock between two warehouse locations — shifting units from an overflow facility to a primary storefront, or reallocating goods after a logistics event — that movement must be recorded in Qoyod to keep inventory balances accurate at both locations. Without it, Qoyod's records show the source location as still holding units that have already left, and the destination location as short of units it already received. Any downstream document — invoices drawn against the source, purchase orders routed to the destination — may reference incorrect stock figures.

This use case covers one direction: your integrated system detects that a transfer event has occurred (whether triggered in real time or as part of a batch sync), then sends a single request to Qoyod to create an inventory transfer. Qoyod immediately deducts the transferred quantity from the source location and credits it to the destination location. The transfer is finalized on creation — there is no draft or pending state to manage.

If this step is skipped or fails silently, the two locations carry divergent balances indefinitely. Reconciling them later requires manual adjustment and produces audit trail gaps.

When to use this

Use this when:

  • Stock has physically moved from one Qoyod inventory location to another and you need to record that movement in Qoyod.
  • Your integrated system generates a transfer or relocation event (e.g., a warehouse management event, a logistics record, or an inter-branch transfer document) that must be reflected in Qoyod's inventory balances.
  • You need Qoyod to immediately deduct quantity from the source location and credit it to the destination location as a single atomic operation.

Do not use this when:

  • You need to correct the recorded quantity at a single location against a physical count — that is an inventory adjustment. See Inventory adjustment (UC-03).
  • You need to create the products involved in the transfer — products must exist in Qoyod before they can appear in a transfer's line items. See New product creation (UC-01).
  • You need to create or manage the inventory locations themselves — both the source and destination locations must already exist in Qoyod before you can reference them in a transfer.

Prerequisites

  • A valid OAuth 2.0 access token, or an API key, for the Qoyod account you are integrating with. Include it as a request header on every call.
  • The Qoyod inventory location ID for the source location (the warehouse stock is leaving). If you do not already hold this ID, retrieve it by calling GET /2.0/inventories and searching by name.
  • The Qoyod inventory location ID for the destination location (the warehouse stock is entering). Obtain it the same way.
  • The Qoyod product ID for each product being transferred. These IDs are obtained at product creation (UC-01) and should be stored in your integration's data store.
  • At least one line item — the line_items array must contain a minimum of one item. Requests with an empty array will return a validation error inside a 200 OK response.

Sequence diagram

Step-by-step

1. Confirm the inventory location IDs

Before building the request, verify you have both location IDs stored: from_location (source) and to_location (destination). Both are integers. If either is missing, call GET /2.0/inventories to retrieve the full list and identify the correct IDs by name. Store them in your integration's data store for reuse — you will need them on every transfer event.

2. Build the request body

Wrap the transfer object under the inventory_transfer key. Include at least one item in line_items. Send quantity as a number (not a string — unlike actual_quantity in inventory adjustments, which is a string).

Request body
{
"inventory_transfer": {
"from_location": 5,
"to_location": 9,
"date": "2026-05-18",
"description": "Reallocation — overflow to main storefront",
"line_items": [
{
"product_id": 88,
"quantity": 30
},
{
"product_id": 91,
"quantity": 15
}
]
}
}

date defaults to today if omitted. description is optional but recommended for audit trail clarity.

3. Send the request

Send the request
curl -X POST "https://api.qoyod.com/2.0/inventory_transfers" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {access_token}" \
-d @request-body.json

Replace {access_token} with your OAuth 2.0 access token. If you are using an API key instead, replace the Authorization header with the header format your Qoyod account uses for API key authentication.

4. Inspect the response body — do not rely on the status code alone

warning

This endpoint returns 200 OK for both success and validation failure. You cannot determine the outcome from the HTTP status code. You must read the response body and check which top-level key is present.

If the response body contains an inventory_transfer key, the transfer was created and finalized successfully. Capture the id field from the returned object and store it.

Response — 200 OK (success)
{
"inventory_transfer": {
"id": 812,
"date": "2026-05-18",
"description": "Reallocation — overflow to main storefront",
"from": { "id": 5, "en_name": "Overflow Warehouse", "ar_name": "مستودع الفائض" },
"to": { "id": 9, "en_name": "Main Storefront", "ar_name": "المتجر الرئيسي" },
"created_at": "2026-05-18T10:00:00.000Z",
"updated_at": "2026-05-18T10:00:00.000Z",
"line_items": [
{
"id": 3301,
"product": { "id": 88, "name": "Widget A" },
"quantity": 30,
"value": 12.5
}
]
}
}

If the response body contains an errors key, validation failed. Read the error messages, correct the request, and retry.

Response — 200 OK (validation failure)
{
"errors": {
"from_location": ["must exist"],
"line_items": ["is invalid"]
}
}

5. Store the transfer ID

Once you confirm the inventory_transfer key is present, store the returned id persistently. There is no endpoint to retrieve a transfer after creation — the response body is the only API-sourced record you will have.

Field mapping

The table below maps concepts from your integrated system to the fields Qoyod expects in the inventory_transfer request body. See the inventory transfers reference for the complete schema.

Integrated system conceptQoyod fieldRequiredNotes
Source warehouse / locationfrom_locationYesInteger. Qoyod inventory ID of the location stock is moving out of.
Destination warehouse / locationto_locationYesInteger. Qoyod inventory ID of the location stock is moving into.
Transfer datedateNoString in YYYY-MM-DD format. Defaults to today if omitted.
Description / notesdescriptionNoFree-text string. Useful for audit trail; not required.
Product (line item)line_items[].product_idYes (per item)Integer. Qoyod product ID of the item being transferred.
Quantity to transferline_items[].quantityYes (per item)Number, not a string. Contrast with actual_quantity in inventory adjustments, which is a string.

from_location and to_location are Qoyod inventory IDs — the integer identifiers returned by GET /2.0/inventories, not human-readable names. Confirm both IDs before constructing the request. quantity in the line item is typed as a number in the spec — pass it as an unquoted JSON number.

Verification

A 200 OK status code does not confirm success — see Step-by-step above. Success is confirmed when the response body contains the inventory_transfer key and the returned object has a non-null integer id.

Check the following in the success response:

  • id is present and is a positive integer. This is your only persistent reference to the transfer record — store it.
  • date reflects the date you submitted, or today's date if you omitted it.
  • from and to objects are present, each with id, en_name, and ar_name matching the locations you intended.
  • line_items contains one entry per product you submitted, each with a quantity value matching what you sent.

Error scenarios

ConditionHTTP statusResponse bodyWhat to do
Validation failure (missing required field, invalid location ID, empty line_items)200 OKBody contains errors key with field-keyed arrays of message stringsRead the errors object, identify the failing fields, correct the request body, and retry.
from_location or to_location absent200 OKBody contains errors keyEnsure both location IDs are present in the inventory_transfer wrapper object.
Invalid location ID (location does not exist in Qoyod)200 OKBody contains errors keyConfirm both inventory location IDs by querying GET /2.0/inventories before submitting.
Invalid product_id in a line item200 OKBody contains errors keyConfirm each product ID against Qoyod. Products must exist before they can be transferred.

The key pattern: any failure this endpoint surfaces comes back as 200 OK with an errors body, not as a 4xx status code. Your integration's error-handling logic must branch on the response body, not on the HTTP status code.

For retry logic and backoff strategy, see error handling.